package trb.complexshield.closedmesh;

import no.hrp.hvrc.rad.calc.intersect.Segment;
import no.hrp.hvrc.rad.calc.intersect.raytrace.CollisionMesh;
import no.hrp.hvrc.rad.calc.intersect.raytrace.Intersection;
import no.hrp.hvrc.rad.calc.intersect.raytrace.RayCollisionPacket;
import no.hrp.hvrc.rad.dosim.bvh.BottumUpBVH;
import no.hrp.hvrc.utils.j3d.BBox;
import trb.complexshield.ComplexShield;

import javax.media.j3d.Transform3D;
import javax.vecmath.Vector3f;

public class ClosedMesh implements ComplexShield {

    public final CollisionMesh collisionMesh;

    private static final ThreadLocal<RayCollisionPacket> rayThreadLocal = new ThreadLocal<RayCollisionPacket>() {
        @Override
        protected RayCollisionPacket initialValue() {
            return new RayCollisionPacket();
        }
    };

    // the shields local to world matrix
    private final Transform3D local2World = new Transform3D();

    // the world to local space transformation matrix
    protected Transform3D world2local = new Transform3D();

    // temp var, the transformed line
    private static final ThreadLocal<Segment> lThreadLocal = new ThreadLocal<Segment>() {
        @Override
        protected Segment initialValue() {
            return new Segment(new Vector3f(), new Vector3f(0, 1, 0));
        }
    };
    private final BBox aabb;
    private final BottumUpBVH.AABB bvhAabb;


    public ClosedMesh(float[] coords, int[] indices, Transform3D t3d) {
        collisionMesh = new CollisionMesh(coords, indices);
        world2local.invert(t3d);
        local2World.set(t3d);
        aabb = new BBox(coords);
        aabb.transform(local2World);
        bvhAabb = new BottumUpBVH.AABB(aabb);
    }

    @Override
    public void intersect(Segment line, Output output) {
        if (!bvhAabb.intersects(line)) {
            return;
        }
        Segment l = lThreadLocal.get();
        l.transform(line, world2local);

        RayCollisionPacket ray = rayThreadLocal.get();

        // get all the intersections between line and mesh
        ray.reset(l.originRef(), l.directionRef());
        collisionMesh.bruteRayCollisionDetection(ray);

        // sort intersections on distance from startPos
        ray.sortResults();

        // check if startPos is inside mesh
        boolean startsInside = (ray.results.size() % 2) == 1;
        boolean isInside = startsInside;

        // calculate distances based on intersections and starts inside
        float endTime = l.length();
        float ooEndTime = 1f / endTime;
        float prevTime = 0;
        float curTime = 0;
        for (Intersection intersection : ray.results) {
            float collisionTime = intersection.time;
            curTime = collisionTime;
            if (curTime > endTime) {
                // exit loop if collision is not on line
                break;
            }
            if (isInside) {
                output.addIntersection(prevTime * ooEndTime, collisionTime * ooEndTime);
            } else {
                prevTime = collisionTime;
            }
            isInside = !isInside;
        }

        if (isInside && ((endTime - prevTime) > 0)) {
            output.addIntersection(prevTime * ooEndTime, endTime * ooEndTime);
        }
    }

    public BBox getAABB() {
        return aabb;
    }
}
